﻿using SBSftpHandler;
using SBSSHCommon;
using SBSSHHandlers;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WindowsFormsApplication1;
using WindowsFormsApplication1.Data;

namespace WindowsFormsApplication1
{
    public class SFTPSession
    {
        private MSLogger _logger;

        private TElSSHSubsystemThread _thread;

        private string _username = null;
        private string _fileExtension = null;

        public SFTPSession(TElSSHTunnelConnection conn, MSLogger logger, string username)
        {
            _logger = logger;

            _username = username;            

            _thread = new TElSSHSubsystemThread(new TElSFTPSSHSubsystemHandler(conn, true), conn, true);
            var handler = (TElSFTPSSHSubsystemHandler)_thread.Handler;

            handler.Server.OnOpen += new SBUtils.TNotifyEvent(onOpen);
            handler.Server.OnReceive += new SBSftpCommon.TElSFTPServerReceiveEvent(onReceive);
            handler.Server.OnClose += new SBUtils.TNotifyEvent(onClose);
            handler.Server.OnCloseHandle += new SBSftpCommon.TElSFTPServerCloseHandleEvent(onCloseHandle);
            handler.Server.OnOpenFile += new SBSftpCommon.TElSFTPServerOpenFileEvent(onOpenFile);
            handler.Server.OnReadFile += new SBSftpCommon.TElSFTPServerReadEvent(onReadFile);
            handler.Server.OnWriteFile += new SBSftpCommon.TElSFTPServerWriteEvent(onWriteFile);
            handler.Server.OnRequestAbsolutePath += new SBSftpCommon.TElSFTPServerRequestAbsolutePathEvent(onRequestAbsolutePath);            

            _thread.Resume();
        }

        #region SFTP Server Event Handlers
        private void onOpen(object sender)
        {
            _logger.Debug("SFTP Server OnOpen event has been triggered");
        }

        private void onClose(object sender)
        {
            _logger.Debug("SFTP Server OnClose event has been triggered");
        }

        private void onReceive(object sender, ref byte[] buffer, int maxSize, ref int written)
        {
            //_logger.Debug("SFTP Server OnReceive event has been triggered");
        }

        private void onOpenFile(object sender, string path, int modes, int access, uint desiredAccess, SBSftpCommon.TElSftpFileAttributes attributes, ref object data, ref int errorCode, ref string comment)
        {
            _logger.Debug("SFTP Server OpenFile event has been triggered");

            try {
                var p = canonicalizePath(path, true);
                _fileExtension = Path.GetExtension(p);

                //_logger.Debug(String.Format("The following path was provided: [{0}]. The following canonicalized path was produced: [{1}]", path, p));

                data = new MemoryStream();

                if ((modes & SBSftpCommon.Unit.fmAppend) == SBSftpCommon.Unit.fmAppend) {
                    ((MemoryStream)data).Position = ((MemoryStream)data).Length;
                }

                errorCode = 0;
            } catch (Exception ex) {
                _logger.Debug(String.Format("ERROR - Error opening file for SFTP client.\r\n\r\n{0}", ex.GetFullException()));

                errorCode = SBSftpCommon.Unit.SSH_ERROR_FAILURE;
                comment = ex.Message;
            }
        }

        private void onReadFile(object sender, object data, long offset, ref byte[] buffer, int bufferOffset, int count, ref int read, ref int errorCode, ref string comment)
        {
            _logger.Debug("SFTP Server ReadFile event has been triggered");
        }

        private void onWriteFile(object sender, object data, long offset, byte[] buffer, int bufferOffset, int count, ref int errorCode, ref string comment)
        {
            _logger.Debug("SFTP Server OnWriteFile event has been triggered");

            try {                
                var stream = (Stream)data;
                stream.Position = offset;
                stream.Write(buffer, bufferOffset, count);                
                
                
                errorCode = 0;
            } catch (Exception ex) {
                _logger.Debug(String.Format("ERROR - Error writing file from SFTP client.\r\n\r\n{0}", ex.GetFullException()));

                errorCode = SBSftpCommon.Unit.SSH_ERROR_FAILURE;
                comment = ex.Message;
            }
        }

        private void onRequestAbsolutePath(object sender, string path, ref string absolutePath, SBSftpCommon.TSBSftpRealpathControl control, SBStringList.TElStringList composePath, ref int errorCode, ref string comment)
        {
            _logger.Debug("SFTP Server RequestAbsolutePath event has been triggered");

            absolutePath = canonicalizePath(path, false);

            _logger.Debug(String.Format("The following path was requested: [{0}]. The following canonicalized path was provided: [{1}]", path, absolutePath));

            errorCode = 0;
        }

        public static byte[] ReadToEnd(System.IO.Stream stream)
        {
            long originalPosition = 0;

            if (stream.CanSeek)
            {
                originalPosition = stream.Position;
                stream.Position = 0;
            }

            try
            {
                byte[] readBuffer = new byte[4096];

                int totalBytesRead = 0;
                int bytesRead;

                while ((bytesRead = stream.Read(readBuffer, totalBytesRead, readBuffer.Length - totalBytesRead)) > 0)
                {
                    totalBytesRead += bytesRead;

                    if (totalBytesRead == readBuffer.Length)
                    {
                        int nextByte = stream.ReadByte();
                        if (nextByte != -1)
                        {
                            byte[] temp = new byte[readBuffer.Length * 2];
                            Buffer.BlockCopy(readBuffer, 0, temp, 0, readBuffer.Length);
                            Buffer.SetByte(temp, totalBytesRead, (byte)nextByte);
                            readBuffer = temp;
                            totalBytesRead++;
                        }
                    }
                }

                byte[] buffer = readBuffer;
                if (readBuffer.Length != totalBytesRead)
                {
                    buffer = new byte[totalBytesRead];
                    Buffer.BlockCopy(readBuffer, 0, buffer, 0, totalBytesRead);
                }
                return buffer;
            }
            finally
            {
                if (stream.CanSeek)
                {
                    stream.Position = originalPosition;
                }
            }
        }

        private void onCloseHandle(object sender, object data, ref int errorCode, ref string comment)
        {
            _logger.Debug("SFTP Server OnCloseHandle event has been triggered");
            try
            {
                byte[] m_Bytes = ReadToEnd(((Stream)data));

                using (var db = new SQLHandler())
                {
                    db.SaveFile(_username, m_Bytes, _fileExtension);
                }
                ((Stream)data).Close();
                errorCode = 0;
            }
            catch (Exception ex)
            {
                _logger.Debug(String.Format("ERROR - Closing SFTP Server handle.\r\n\r\n{0}", ex.GetFullException()));

                errorCode = SBSftpCommon.Unit.SSH_ERROR_FAILURE;
                comment = ex.Message;
            }
        }
        #endregion

        private string canonicalizePath(string relpath, bool local)
        {
            char[] delims = new char[1];
            delims[0] = '/';
            string[] pathelems = relpath.Split(delims, 1024);
            ArrayList lst = new ArrayList();

            for (int i = 0; i < pathelems.Length; i++) {
                string elem = (string)pathelems[i];
                if (elem == "..") {
                    if (lst.Count > 0) {
                        lst.RemoveAt(lst.Count - 1);
                    }
                } else if ((elem != ".") && (elem != "")) {
                    lst.Add(elem);
                }
            }

            string result;
            string separator;

            if (local) {
                result = "C:\\";
                separator = "\\";
            } else {
                result = "/";
                separator = "/";
            }

            for (int i = 0; i < lst.Count; i++) {
                result = result + (string)lst[i];
                if (i != lst.Count - 1) {
                    result = result + separator;
                }
            }

            return result;
        }
    }
}
